#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import uuid
import socket

BUF_SIZE = 4096


class RequestConnection(object):
    connections = {}
    clients_num = 0

    def __init__(self, sid=None, sock=None, addr=None):
        if not sid:
            sid = str(uuid.uuid4())
        self.id = sid
        self.sock = sock
        self.addr = addr
        self.user = None
        self.login_from = 'ST'
        self.clients = {}

    def __str__(self):
        return '<{} from {}>'.format(self.user, self.addr)

    def new_client(self, cid, chan=None):
        client = Client(
            cid=cid, user=self.user, addr=self.addr,
            login_from=self.login_from
        )
        client.connection_id = self.id
        self.clients[cid] = client
        self.__class__.clients_num += 1
        print("New client {} join, total {} now".format(
            client, self.__class__.clients_num
        ))
        return client

    def get_client(self, cid):
        if hasattr(cid, 'get_id'):
            cid = cid.get_id()
        client = self.clients.get(cid)
        return client

    def remove_client(self, cid):
        client = self.get_client(cid)
        if not client:
            return
        client.close()
        self.__class__.clients_num -= 1
        self.clients.pop(cid, None)
        print("Client {} leave, total {} now".format(
            client, self.__class__.clients_num
        ))

    def close(self):
        clients_copy = [k for k in self.clients]
        for cid in clients_copy:
            self.remove_client(cid)

    @classmethod
    def new_connection(cls, addr, sid=None, sock=None):
        if not sid:
            sid = str(uuid.uuid4())
        connection = cls(sid=sid, sock=sock, addr=addr)
        cls.connections[sid] = connection
        return connection

    @classmethod
    def remove_connection(cls, sid):
        connection = cls.get_connection(sid)
        if not connection:
            return
        connection.close()
        cls.connections.pop(sid, None)

    @classmethod
    def get_connection(cls, sid):
        return cls.connections.get(sid)


class Request(object):
    def __init__(self):
        self.type = None
        self.x11 = None
        self.kind = None
        self.meta = {'env': {}}


class Client(object):
    """
    Client is the request client. Nothing more to say

    ```
    client = Client(chan, addr, user)
    ```
    """

    def __init__(self, cid=None, user=None, addr=None, login_from=None, chan=None):
        if cid is None:
            cid = str(uuid.uuid4())
        self.id = cid
        self.user = user
        self.addr = addr
        self.chan = chan   # 使用WSProxy的通道与客户端和服务器通信
        self.request = Request()
        self.login_from = login_from
        self.connection_id = None

    def fileno(self):
        return self.chan.fileno()

    def send(self, b):
        try:
            return self.chan.send(b)
        except (OSError, EOFError, socket.error) as e:
            print('Send to client {} error: {}'.format(self, e))
            return 0

    def send_unicode(self, s):
        b = s.encode()
        self.send(b)

    @property
    def closed(self):
        return self.chan.closed

    def recv(self, size):
        return self.chan.recv(size)

    def close(self):
        print("Client {} close".format(self))
        if self.chan:
            self.chan.close()
        return

    def __getattr__(self, item):
        return getattr(self.chan, item)

    def __str__(self):
        return "<%s from %s:%s>" % (self.user, self.addr[0], self.addr[1])


class WSProxy(object):
    def __init__(self, ws, client_id):
        self.ws = ws
        self.client_id = client_id

        # 1、socket.socketpair函数仅返回两个已经连接的套接字对象，参数和socket.socket()里的参数一样的用法。
        # 2、socket.socketpair可以理解为创建了两个socket, 比喻为一个server的socket，一个client的socket，这两个socket是已经connected连接状态
        # 3、socket.socketpair是全双工模式，也就是每个socket都能收发，比喻为server.send - -->client.recv, 和 client.send - -->server.recv
        # 4、socket.socketpair默认是创建unix套接字
        self.sock, self.proxy = socket.socketpair()

    def send(self, data):
        """
        服务器端发送数据到客户端
        :param data:
        :return:
        """
        _data = {'data': data.decode(errors="ignore"), 'room': self.client_id}
        self.ws.emit("data", _data, room=self.client_id)

    def write(self, data):
        """
        将客户端的数据写到通道中
        :param data:
        :return:
        """
        self.proxy.send(data.encode())

    def recv(self, size):
        """
        服务器端接收客户端的数据 ( 从通道中获取数据 )
        :param size:
        :return:
        """
        return self.sock.recv(size)

    def close(self):
        self.proxy.close()

    @property
    def closed(self):
        return self.sock._closed

    def __getattr__(self, item):
        return getattr(self.sock, item)


class Server(object):
    """
    SSH Server
    """

    def __init__(self, chan, conn, asset, system_user):
        """ invoke shell instance == chan """
        self.chan = chan
        self.connection = conn
        self.asset = asset
        self.system_user = system_user

    def fileno(self):
        return self.chan.fileno()

    def send(self, b):
        try:
            return self.chan.send(b)
        except (OSError, EOFError, socket.error) as e:
            print('Send to client {} error: {}'.format(self, e))
            return 0

    def send_unicode(self, s):
        b = s.encode()
        self.send(b)

    def recv(self, size):
        return self.chan.recv(size)

    def close(self):
        self.chan.close()
        self.connection.close()

    @property
    def closed(self):
        return self.chan._closed
